﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace Upreader.Usenet.Nntp
{
    public class NntpSocket : INntpSocket
    {
        #region Configuration

        public static readonly int DefaultTimeOut = -1;

        #endregion // Configuration

        #region Fields

        private readonly string _address;
        private readonly int _port;
        private readonly bool _isSecuredConnection;

        private TcpClient _connection;
        private Stream _stream;
        private bool _disposed;
        private int _timeOut = DefaultTimeOut;

        #endregion // Fields

        #region Constructor

        public NntpSocket(string address, int port, bool isSecuredConnection)
        {
            if (address == null)
                throw new ArgumentNullException("address");

            if (port <= 0)
                throw new ArgumentException(Resource.NntpSocket_InvalidPort, "port");

            _address = address;
            _port = port;
            _isSecuredConnection = isSecuredConnection;
        }

        #endregion // Constructor

        #region Properties

        public int ConnectionTimeout
        {
            get { return _timeOut; }
            set 
            {
                if (_timeOut < -1)
                {
                    throw new ArgumentException(Resource.NntpSocket_InvalidTimeOut);
                }
                _timeOut = value; 
            }
        }

        public string Address
        {
            get { return _address; }
        }

        public int Port
        {
            get { return _port; }
        }

        public bool IsSecuredConnection
        {
            get { return _isSecuredConnection; }
        }

        public bool IsConnected
        {
            get { return _connection != null && _connection.Connected; }
        }

        public Stream TransportStream
        {
            get
            {
                ValidateConnectionState();

                return _stream;
            }
        }   

        #endregion // Properties

        #region Methods

        public void Connect()
        {
            if (!IsConnected)
            {
                _connection = CreateTcpClient();

                Stream rawStream = _connection.GetStream();
                if (IsSecuredConnection)
                {
                    try
                    {
                        SslStream sslStream = new SslStream(rawStream, false, ValidateServerCertificate);
                        sslStream.AuthenticateAsClient(Address);

                        _stream = sslStream;
                    }
                    catch
                    {
                        rawStream.Dispose();
                        throw;
                    }
                }
                else
                {
                    _stream = rawStream;
                }
            }
        }
        
        public void Disconnect()
        {
            if (IsConnected)
            {
                _stream.Close();
                _stream = null;

                _connection.Close();
                _connection = null;
            }
        }

        #endregion // Methods

        #region Helpers

        /// <summary>
        /// Used to validate the server certificate when authentication to the server as a socket
        /// </summary>
        protected bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            // lets be open and accept anything
            return true;
        }

        protected virtual TcpClient CreateTcpClient()
        {
            return new TcpClient(Address, Port);
        }

        /// <summary>
        /// Validates the state of the connection.
        /// </summary>
        protected void ValidateConnectionState()
        {
            if (!IsConnected) 
            {
                throw new InvalidOperationException(Resource.NntpSocket_NotConnected);
            }
        }

        #endregion // Helpers

        #region Dispose

        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                Disconnect();

                _disposed = true;
            }
        }

        ~NntpSocket()
        {
            Dispose(false);
        }

        #endregion // Dispose
    }
}
